12 Design
Patterns:
Author: RAMU MEDA
Date: 01/sep/03
Iterator:
- Also known as
Cursor.
- Provide a way to
access the elements of an aggregate object sequentially without exposing
its underlying representation.
- To traverse the aggregate
in different ways, depending on what you want to accomplish.
- to have more than
one traversal pending on the same aggregate object
- The key idea in
this pattern is to take the responsibility for access and traversal out of
the aggregate object and put it into an iterator object
- The Iterator class
defines an interface for accessing the aggregate’s elements.
- An iterator object
is responsible for keeping track of the current element; that is, it knows
which elements have been traversed already.
- Separating the
traversal mechanism from the List object lets us define iterators for
different traversal policies without enumerating them in the List
interface. For example, FilteringListIterator might provide access only to
those elements that satisfy specific filtering constraints.
- Use the Iterator pattern
- to access an aggregate object's
contents without exposing its
internal representation.
- to support multiple traversals of aggregate
objects.
- to provide a uniform interface for traversing
different aggregate structures (that is, to support polymorphic
iteration).
- The Iterator
pattern has three important consequences:
- It supports
variations in the traversal of an aggregate. Complex aggregates may be
traversed in many ways. For example, code generation and semantic
checking involve traversing parse trees. Code generation may traverse the
parse tree inorder or preorder. Iterators make it easy to change the
traversal algorithm: Just replace the iterator instance with a different one. You
can also define Iterator subclasses to support new traversals.
- Iterators simplify the Aggregate interface. Iterator's
traversal interface obviates the need for a similar interface in
Aggregate, thereby simplifying the aggregate's interface.
- More than one
traversal can be pending on an aggregate. An iterator keeps track of its
own traversal state. Therefore you can have more than one traversal in progress at once.
- external iterator / Active iterator à client controls the iteration à Clients that use an external
iterator must advance the traversal and request the next element
explicitly from the iterator à more flexible e.g.: easy to
compare two collections for equality
- Internal iterator / Passive Iteratorà the iterator controls iteration. à The client hands
an internal iterator an operation to perform, and the iterator applies
that operation to every element in the aggregate. à easier to use, because they
define the iteration logic for you.
- It’s easier just to
use an internal
iterator (rather than external iterator) for recursive aggregate structures like composite. It can record the
current position simply by calling itself recursively, thereby storing the
path implicitly in the call stack.
- Cursor à iterator to store just the
state of the iteration. It merely points to the current position in the
aggregate à aggregate defines the
traversal algorithm
- Cursors are a simple example of the
Memento pattern and share many of its implementation issues.
- Why Cursor?? à If the iterator is
responsible for the traversal algorithm, then it's easy to use different
iteration algorithms on the same aggregate, and it can also be easier to
reuse the same algorithm on different aggregates. On the other hand, the
traversal algorithm might need to access the private variables of the
aggregate. If so, putting the traversal algorithm in the iterator violates
the encapsulation of the aggregate.
- Robust iterator à ensures that insertions and
removals won't interfere with traversal, and it does it without copying
the aggregate.
- NullIterator à is a degenerate iterator
that's helpful for handling boundary conditions. By definition, a NullIterator is always done
with traversal; that is, its IsDone operation always evaluates to true.
- Composite: Iterators are often applied
to recursive structures such as Composites.
- Factory Method: Polymorphic
iterators rely on factory methods to instantiate the appropriate Iterator
subclass.
- Memento : is often used in
conjunction with the Iterator pattern. An iterator can use a memento to
capture the state of an iteration. The iterator stores the memento internally.
MEDIATOR Pattern:
- Define an object
that encapsulates how
a set of objects interact.
- promotes loose coupling by keeping objects from
referring to each other explicitly, and it lets you vary their interaction independently
- Colleagues send and receive requests
from a Mediator object.
- The mediator
implements the cooperative behavior by routing requests between the
appropriate colleague(s).
- It limits subclassing. A mediator
localizes behavior that otherwise would be distributed among several
objects. Changing this behavior requires subclassing Mediator only;
Colleague classes can be reused as is.
- It decouples colleagues. A mediator
promotes loose coupling between colleagues. You can vary and reuse
Colleague and Mediator classes independently.
- It simplifies object protocols. A mediator
replaces many-to-many interactions with one-to-many interactions between
the mediator and its colleagues. One-to-many relationships are easier to
understand, maintain, and extend.
- It abstracts how objects cooperate. Making mediation
an independent concept and encapsulating it in an object lets you focus on
how objects interact apart from their individual behavior. That can help
clarify how objects interact in a system.
- It centralizes control. The Mediator
pattern trades complexity of interaction for complexity in the mediator.
Because a mediator encapsulates protocols, it can become more complex than
any individual colleague. This can make the mediator itself a monolith
that's hard to maintain.
- Facade differs from
Mediator in that it abstracts a subsystem of objects to provide a more
convenient interface. Its protocol is unidirectional; that is, Facade objects make
requests of the subsystem classes but not vice versa. In contrast,
Mediator enables cooperative behavior that colleague objects don't or
can't provide, and the protocol is multidirectional.
- Colleagues can communicate with the
mediator using the Observer pattern.
Colleagues have to communicate with their mediator when an event of
interest occurs. One approach is to implement the Mediator as an Observer using the Observer
pattern. Colleague
classes act as Subjects, sending notifications to the mediator whenever they
change state. The mediator responds by propagating the effects of the
change to other colleagues
- There's no need to
define an abstract
Mediator class when colleagues work with only one mediator. The
abstract coupling that the Mediator class provides lets colleagues work
with different Mediator subclasses, and vice versa
FAÇADE Pattern:
- Provide a unified interface to a set of interfaces in a subsystem.
- defines a higher-level interface that makes the subsystem easier to use
- Provides a single, simplified interface to the more
general facilities of a subsystem.
- Clients communicate
with the subsystem by sending requests to Facade, which forwards them to
the appropriate subsystem object(s).
- Although the
subsystem objects perform the actual work, the facade may have to do work
of its own to translate its interface to subsystem interfaces.
- Clients that use
the facade don't have to access its subsystem objects directly.
- Facade
·
Knows which subsystem classes are responsible for a
request.
·
Delegates client requests to appropriate subsystem objects.
·
Implement subsystem functionality.
·
Handle work assigned by the Facade object.
·
Have
no knowledge of the facade; that is, they keep no references to it.
- The Facade pattern
offers the following benefits:
- It shields clients
from subsystem components, thereby reducing the number of objects that clients deal
with
and making the subsystem easier to use.
- It promotes weak
coupling between the subsystem and its clients. Often the components in a
subsystem are strongly coupled. Weak coupling lets you vary the
components of the subsystem without affecting its clients. Facades help
layer a system and the dependencies between objects. They can eliminate complex or circular
dependencies. This can be an important consequence when the
client and the subsystem are implemented independently.
- Reducing compilation dependencies is vital in large
software systems. You want to save time by minimizing recompilation when
subsystem classes change. Reducing compilation dependencies with facades
can limit the recompilation needed for a small change in an important
subsystem. A facade can also simplify porting systems to other platforms,
because it's less likely that building one subsystem requires building
all others.
- It doesn't prevent applications from
using subsystem classes if they need to. Thus you can choose between
ease of use and generality.
- Use the Facade
pattern when
·
You want to provide a simple interface to a complex subsystem. Subsystems often get
more complex as they evolve. Most patterns, when applied, result in more and
smaller classes. This makes the subsystem more reusable and easier to
customize, but it also becomes harder to use for clients that don't need to
customize it. A facade can
provide a simple default view of the subsystem that is good enough for most
clients. Only clients needing more customizability will need to look beyond the
facade.
·
There are many dependencies between clients and the
implementation classes of an abstraction. Introduce a facade to decouple the subsystem from clients and
other subsystems, thereby promoting subsystem independence and portability.
·
You want to
layer your subsystems. Use a facade
to define an entry point to each subsystem level. If subsystems are
dependent, then you can simplify the dependencies between them by making them
communicate with each other solely through their facades.
1.
Abstract
Factory
can be used with Facade to provide an interface for creating subsystem objects
in a subsystem-independent way. Abstract Factory can also be used as an
alternative to Facade to hide platform-specific classes.
2.
Mediator is similar to Facade in
that it abstracts
functionality of existing classes. However, Mediator's purpose is to abstract
arbitrary communication between colleague objects, often centralizing functionality that doesn't
belong in any one of them. A mediator's colleagues are aware of and communicate with
the mediator instead of communicating with each other directly. In contrast, a facade merely abstracts the interface to
subsystem objects to make them easier to use; it doesn't define new functionality, and subsystem classes don't know about it.
3.
Usually only one Facade object is required. Thus Facade
objects are often Singletons .
OBSERVER
Pattern:
- Also Known As Dependents, Publish-Subscribe
- Define a one-to-many dependency between objects so
that when one object changes state, all its dependents are notified and
updated automatically.
- Maintain consistency between related objects by making the
classes loosely
coupled, because that increases their reusability.
- Abstract coupling between Subject and
Observer. All a subject knows is that it has a list of observers, each
conforming to the simple interface of the abstract Observer class. The
subject doesn't know the concrete class of any observer. Thus the coupling
between subjects and observers is abstract and minimal. Because
Subject and Observer aren't tightly coupled, they can belong to different
layers of abstraction in a system. A lower-level subject can communicate
and inform a higher-level observer, thereby keeping the system's layering
intact
- Support for broadcast communication. Unlike an ordinary
request, the notification that a subject sends needn't specify its
receiver. The notification is broadcast automatically to all interested objects that subscribed
to it.
The subject doesn't care how many interested objects exist; its only
responsibility is to notify its observers. This gives you the freedom to
add and remove observers at any time. It's up to the observer to handle or
ignore a notification.
- Unexpected
updates. Because observers have no knowledge of each other's presence,
they can be blind to the ultimate cost of changing the subject. A
seemingly innocuous operation on the subject may cause a cascade of
updates to observers and their dependent objects. Moreover, dependency
criteria that aren't well-defined or maintained usually lead to spurious
updates, which can be hard to track down.
- Use the Observer
pattern in any of the following situations:
- When an
abstraction has two aspects, one dependent on the other. Encapsulating
these aspects in separate objects lets you vary and reuse them independently.
- When a change to
one object requires changing others, and you don't know how many objects need to be changed.
- When an object
should be able to notify
other objects without making assumptions about who these objects are. In other words,
you don't want these objects tightly coupled.
PROTOTYPE
Pattern:
- Specify the kinds of objects to create using a
prototypical instance, and create new objects by copying this prototype.
- Prototype à declares an interface for cloning itself.
- ConcretePrototype à implements an operation for cloning itself.
- Client à creates a new object by asking a prototype to clone itself.
- A client can install and remove prototypes at run-time.
- Specifying new objects by varying
values. Highly dynamic systems let you define new behavior through object composition—by specifying
values for an object's variables, for example—and not by defining new
classes. You effectively define new kinds of objects by instantiating
existing classes and registering the instances as prototypes of client
objects. A client can exhibit new behavior by delegating responsibility to
the prototype. This kind of design lets users define new
"classes" without programming. In fact, cloning a prototype is
similar to instantiating a class. The Prototype pattern can greatly reduce
the number of classes a system needs.
- Specifying new objects by varying
structure. Many applications build objects from parts and subparts. Editors
for circuit design, for example, build circuits out of subcircuits.1 For
convenience, such applications often let you instantiate complex,
user-defined structures, say, to use a specific subcircuit again and
again. The Prototype pattern supports this as well. We simply add this
subcircuit as a prototype to the palette of available circuit elements. As
long as the composite circuit object implements Clone as a deep copy,
circuits with different structures can be prototypes.
- Reduced subclassing. Factory Method
often produces a hierarchy of Creator classes that parallels the product
class hierarchy. The
Prototype pattern lets you clone a prototype instead of asking a factory
method to make a new object. Hence you don't need a Creator class hierarchy at all.
- Configuring an application with
classes dynamically. Some run-time environments let you load classes into
an application dynamically
- Use the Prototype
pattern
- when a system should be independent of how
its products are created, composed, and represented; and
- when the classes
to instantiate are specified at run-time, for example, by dynamic
loading; or to avoid building a class hierarchy of factories that
parallels the class hierarchy of products; or
- When instances of a class can have
one of only a few different combinations of state. It may be more
convenient to install a corresponding number of prototypes and clone them
rather than instantiating the class manually, each time with the
appropriate state.
- The main liability of the Prototype
pattern is that
- Each subclass of Prototype must
implement the Clone operation, which may be difficult. For example,
adding Clone is difficult when the classes under consideration already
exist.
- Implementing Clone
can be difficult when their internals include objects that don't support copying or have
circular references
- Prototype and Abstract Factory are competing
patterns in some ways, as we discuss at the end of this chapter. They can
also be used together, however. An Abstract Factory might store a set of
prototypes from which to clone and return product objects.
- Designs that make
heavy use of the Composite and Decorator patterns often can benefit from
Prototype as well.
Proxy
Pattern:
- Also Known As surrogate
- Intent à Provide a surrogate or placeholder for another object
to control access to it.
- One reason for
controlling access to an object is to defer the full cost of its creation and
initialization until we actually need to use it.
- Proxy is applicable
whenever there is a need for a more versatile or sophisticated reference to an
object than a simple pointer.
- Remote proxy à also called Ambassador à provides a local
representative for an object in a different address space.
- Remote proxy can
hide the fact that an object resides in a different address space.
- Virtual proxy à creates expensive objects on
demand.
- Protection proxy à controls access to the
original object. Protection proxies are useful when objects should have
different access rights.
- Smart reference à is a replacement for a bare
pointer that performs additional actions when an object is accessed.
Typical uses include
- Counting the number of references to the real
object so that it can be freed automatically when there are no more
references (also called smart
pointers [Ede92]).
- Loading a
persistent object into
memory when it's first referenced.
- Checking that the
real object is locked before it's
accessed to ensure that no other object can change it.
- Both protection proxies and smart
references allow additional housekeeping tasks when an object is accessed.
- Provides an interface identical to Subject's so that a proxy
can by substituted for the real subject.
- Controls access to
the real subject and may be responsible for creating and deleting it.
- Proxy forwards requests to RealSubject when appropriate,
depending on the kind of proxy.
- copy-on-write à related to
creation on demand à another optimization that the Proxy pattern can hide
from the client. à Copying a large and complicated object only if it is by using proxy.
- Vs Adapter à an adapter provides a different interface to the object it
adapts. In contrast, a proxy provides the same interface as its subject. However, a proxy
used for access protection might refuse to perform an operation that the
subject will perform, so its interface may be effectively a subset of the
subject's.
- Vs Decorator à although decorators can have
similar implementations as proxies, decorators have a different purpose. A decorator adds one or more
responsibilities to an object, whereas a proxy controls access to an
object.
- Proxies vary in the
degree to which they are implemented like a decorator. A protection proxy might be
implemented exactly like a decorator.
- On the other hand,
a remote proxy will
not contain a direct reference to its real subject but only an
indirect reference, such as "host ID and local address on host."
A virtual proxy will
start off with an indirect reference such as a file name but will
eventually obtain and use a direct reference.
ADAPTER
Pattern:
·
Also Known As Wrapper ( Decorator is also
known as Wrapper )
·
Intent à to convert the interface of a class into another
interface clients expect.
·
Adapter lets classes work together that couldn't otherwise
because of incompatible
interfaces.
·
Participants
o
Target à defines the domain-specific
interface that Client uses.
o
Client à collaborates with objects conforming to the Target interface.
o
Adaptee à defines an existing interface that needs adapting.
o
Adapter à adapts the interface of Adaptee to the Target interface.
·
Clients call operations on an Adapter instance. In turn,
the adapter calls Adaptee operations that carry out the request.
·
Use the Adapter pattern when
o
You want
to use an existing class, and its interface does not match the one you need.
o
You want to
create a reusable class that cooperates with unrelated or unforeseen classes, that is, classes that
don't necessarily have compatible interfaces.
o
(Object adapter only) you need to use several existing
subclasses, but it's impractical to adapt their interface by subclassing every
one. An object adapter can
adapt the interface of its parent class.
·
Vs Bridge has a structure similar to an
object adapter, but Bridge has a different intent: It is meant to separate an
interface from its implementation so that they can be varied easily and
independently. An adapter is meant to change the interface of an existing
object.
·
Vs Decorator enhances another object
without changing its
interface.
A decorator is thus more transparent to the application than an adapter
is. As a consequence, Decorator
supports recursive composition, which isn't possible with pure adapters.
·
Vs Proxy defines a
representative or surrogate for another object and does not change its interface
·
class
adapter
o
Adapts Adaptee to Target by committing to a concrete Adapter class. As a consequence, a
class adapter won't work when we want to adapt a class
and all its subclasses.
o
Lets Adapter override some of Adaptee's behavior, since Adapter is a subclass of
Adaptee.
o
Introduces
only one object and no additional pointer indirection is needed to get to the adaptee.
·
object
adapter
o
Lets a single
Adapter work with many Adaptees that is, the Adaptee itself and all of its
subclasses (if any). The Adapter can also add functionality to all Adaptees at once.
o
Makes it harder
to override Adaptee behavior. It will require subclassing Adaptee and making Adapter refer
to the subclass rather than the Adaptee itself.
·
Pluggable
adapters
à classes with built-in interface adaptation à a class is more reusable when you
minimize the assumptions other classes must make to use it. à
Interface adaptation lets us incorporate our class into existing systems
that might expect different interfaces to the class.
·
Two-way
adapters à to provide transparency à an adapted object no longer conforms to the Adaptee interface, so it
can't be used as is wherever an Adaptee object can. Two-way adapters can
provide such transparency. à Specifically, they're useful when two different clients need to
view an object differently.
COMPOSITE
Pattern:
- Compose objects
into tree structures to represent part-whole hierarchies.
- Composite lets clients treat individual objects and
compositions of objects uniformly using recursive composition.
- Defines class hierarchies consisting of
primitive objects and composite objects. Primitive objects can be composed
into more complex objects, which in turn can be composed, and so on
recursively. Wherever client code expects a primitive object, it can also
take a composite object.
- Makes the client simple. Clients can treat
composite structures and individual objects uniformly. Clients normally
don't know (and shouldn't care) whether they're dealing with a leaf or a
composite component. This simplifies client code, because it avoids having
to write tag-and-case-statement-style functions over the classes that
define the composition.
- Makes it easier to add new kinds of components. Newly defined
Composite or Leaf subclasses work automatically with existing structures
and client code. Clients don't have to be changed for new Component
classes.
- Can make your design overly general. The disadvantage
of making it easy to add new components is that it makes it harder to restrict the components of a
composite. Sometimes you want a composite to have only certain components.
With Composite, you can't rely on the type system to enforce those
constraints for you. You'll have to use run-time checks instead.
Clients use the Component class
interface to interact with objects in the composite structure. If the recipient
is a Leaf, then the request is handled directly. If the recipient is a
Composite, then it usually forwards requests to its child components, possibly
performing additional operations before and/or after forwarding
- Participants
- Component
- declares the interface
for objects in the composition.
- implements
default behavior for the interface common to all classes, as
appropriate.
- declares an
interface for accessing and managing its child components.
- (optional)
defines an interface for accessing a component's parent in the recursive
structure, and implements it if that's appropriate.
- Leaf
- represents leaf
objects in the composition. A leaf has no children.
- defines behavior
for primitive objects in the composition.
- Composite
- defines behavior
for components having children.
- stores child
components.
- implements
child-related operations in the Component interface.
- Client à manipulates objects in the
composition through the Component interface.
- Often the
component-parent link is used for a Chain of Responsibility.
- Decorator is often used with Composite.
When decorators and composites are used together, they will usually have a
common parent class. So decorators will have to support the Component
interface with operations like Add, Remove, and GetChild.
- Flyweight lets you share components,
but they can no longer refer to their parents.
- Iterator can be used to traverse
composites.
- Visitor localizes operations and
behavior that would otherwise be distributed across Composite and Leaf
classes.
COMMAND Pattern:
- Also Known As Action, Transaction
- Intent à Encapsulate a request as an object, thereby letting
you parameterize
clients with different requests, queue or log requests, and support undoable operations.
- To issue requests
to objects without knowing anything about the operation being requested or
the receiver of the request.
- Consequences:
- Command decouples the object that invokes the
operation from the one that knows how to perform it. i.e. decouple invoker from receiver
- Commands are first-class objects. They can be
manipulated and extended like any other object.
- You can assemble
commands into a composite
command.. In general, composite commands are an instance of the
Composite pattern.
- It's easy to add new Commands, because you
don't have to change existing classes.
- Use the Command
pattern when you want to
- Parameterize objects by an action to
perform.. Commands are an object-oriented replacement for callbacks.
- Specify, queue, and execute requests
at different times. A Command object can have a lifetime independent of
the original request. If the receiver of a request can be represented in
an address space-independent way, then you can transfer a command object
for the request to a different process and fulfill the request there.
- Support undo. The Command's Execute
operation can store state for reversing its effects in the command
itself. The Command interface must have an added Unexecute operation that
reverses the effects of a previous call to Execute. Executed commands are
stored in a history list. Unlimited-level undo and redo is achieved by
traversing this list backwards and forwards calling Unexecute and
Execute, respectively.
- Support logging changes so that they can
be reapplied in case of a system crash. By augmenting the Command interface with load and
store operations, you can keep a persistent log of changes. Recovering
from a crash involves reloading logged commands from disk and reexecuting
them with the Execute operation.
- Structure a system around high-level
operations built on primitives operations. Such a structure is common
in information systems that support transactions. A transaction
encapsulates a set of changes to data. The Command pattern offers a way
to model transactions. Commands have a common interface, letting you
invoke all transactions the same way. The pattern also makes it easy to
extend the system with new transactions.
Collaborations
·
The client creates a ConcreteCommand object and specifies its receiver.
·
An Invoker object stores the
ConcreteCommand object.
·
The invoker issues a request by calling Execute on the command. When commands are
undoable, ConcreteCommand stores state for undoing the command prior to invoking
Execute.
·
The ConcreteCommand object invokes operations on its
receiver to carry out the request.
·
Command decouples the invoker from the receiver (and the
request it carries out).
Participants:
·
Command à declares an interface for executing an operation.
·
ConcreteCommand à
- Defines a
binding between a Receiver object and an action.
- Implements
Execute by invoking the corresponding operation(s) on Receiver.
·
Client (Application) à creates a ConcreteCommand object
and sets its receiver.
·
Invoker à asks the command to carry out the
request.
·
Receiver à
knows how to perform the operations associated with carrying out a
request. Any class may serve as a Receiver.
- A Composite can be used to implement MacroCommands.
- A Memento can keep state the command
requires to undo its effect.
- A command that must
be copied before being placed on the history list acts as a Prototype
BRIDGE Pattern:
- Also Known As Handle/Body
- Decouple an abstraction from its
implementation so that the two can vary independently.
- Inheritance binds an implementation to
the abstraction permanently, which makes it difficult to modify, extend,
and reuse abstractions and implementations independently
- Use the Bridge pattern when
- You want to avoid a permanent binding between an
abstraction and its implementation. This might be the case, for
example, when the implementation must be selected or switched at
run-time.
- Both the abstractions and their
implementations should be extensible by subclassing. In this case,
the Bridge pattern lets you combine the different abstractions and
implementations and extend them independently.
- Changes in the implementation of an
abstraction should have no impact on clients; that is, their code should
not have to be recompiled.
- You want to share an implementation among
multiple objects (perhaps using reference counting), and this fact
should be hidden from the client.
- Abstraction forwards client
requests to its Implementor object.
- consequences:
- Decoupling interface and
implementation. An implementation is not bound permanently to an
interface. The implementation of an abstraction can be configured at
run-time. It's even possible for an object to change its implementation
at run-time.
- Decoupling
Abstraction and Implementor also eliminates compile-time dependencies on the
implementation. Changing an implementation class doesn't require
recompiling the Abstraction class and its clients. This property is
essential when you must ensure binary compatibility between different
versions of a class library.
- Furthermore, this decoupling encourages layering
that can lead to a better-structured system. The high-level part of a
system only has to know about Abstraction and Implementor.
- Improved extensibility. You can extend
the Abstraction and Implementor hierarchies independently.
- Hiding implementation details from
clients. You can shield clients from implementation details, like the sharing of implementor objects and the accompanying reference count mechanism (if
any).
- An Abstract Factory can create and
configure a particular Bridge.
- The Adapter pattern is geared toward making
unrelated classes work together. It is usually applied to systems after they're designed. Bridge, on the other hand, is used up-front in a design to let
abstractions and implementations vary independently.
FLYWEIGHT
Pattern:
- Intent à Use sharing to support large numbers of fine-grained objects efficiently.
- A flyweight is a shared object that can be used in multiple contexts
simultaneously.
- The flyweight acts
as an independent
object in each context—it's indistinguishable from an instance of the object
that's not shared.
- Flyweights cannot
make assumptions about the context in which they operate.
- Intrinsic state à stored in the flyweight à information that's
independent of the flyweight's context à sharable.
- Extrinsic state à depends on and varies with the flyweight's context àcan't be shared. à Client objects are
responsible for passing extrinsic state to the flyweight when it needs it.
- Flyweights model concepts or entities that are normally
too plentiful to represent with objects.
- The Flyweight
pattern's effectiveness depends heavily on how and where it's used. Apply
the Flyweight pattern when all of the following are true:
- An application
uses a large number
of objects.
- Storage costs are high because of the
sheer quantity of objects.
- Most object state can be made
extrinsic.
- Many groups of objects may be
replaced by relatively few shared objects once extrinsic state is removed.
- The application doesn't depend on object identity. Since flyweight
objects may be shared, identity tests will return true for conceptually
distinct objects.
- Savings = f
(flyweights no. + amt of intrinsic state + amt of extrinsic state +
extrinsic stored/computed )
- The more flyweights
are shared, the greater the storage savings. The savings increase with the
amount of shared state.
- The greatest savings occur when the
objects use substantial quantities of both intrinsic and extrinsic state,
and the extrinsic state can be computed rather than stored. Then you save on
storage in two ways: Sharing
reduces the cost of intrinsic state, and you trade extrinsic state for
computation time.
- Because objects are
shared, clients shouldn't instantiate them directly. FlyweightFactory lets clients locate a particular flyweight.
- FlyweightFactory
objects often use an associative
store
to let clients look up flyweights of interest
- Sharability also
implies some form of reference
counting or garbage collection to reclaim a flyweight's
storage when it's no longer needed.
- Related Patterns:
- The Flyweight
pattern is often combined with the Composite pattern to implement a logically
hierarchical structure in terms of a directed-acyclic graph with shared leaf nodes.
- It's often best to
implement State and
Strategy objects as flyweights.
MEMENTO
Pattern:
- Also Known As Token
- Intent à Without violating encapsulation, capture and externalize an object's
internal state so that the object can be restored to this state later.
- A memento is an object that stores a snapshot of the internal
state of another object—the memento's originator.
- The undo mechanism
will request a memento from the originator when it needs to checkpoint the
originator's state. The originator initializes the memento with
information that characterizes its current state. Only the originator can store and
retrieve information from the memento—the memento is "opaque" to
other objects.
- Use the Memento
pattern when
- a snapshot of
(some portion of) an object's state must be saved so that it can be
restored to that state later, and
- a direct interface
to obtaining the state would expose implementation details and break the
object's encapsulation
Participants:
·
Memento
o
stores internal state of the Originator object. The memento
may store as much or as little of the originator's internal state as necessary
at its originator's discretion.
o
protects against access by objects other than the
originator. Mementos have effectively two interfaces. Caretaker sees a narrow interface to the Memento—it can only pass
the memento to other objects. Originator, in contrast, sees a wide interface, one that lets it access all the
data necessary to restore itself to its previous state. Ideally, only the
originator that produced the memento would be permitted to access the memento's
internal state.
·
Originator
o
creates a memento containing a snapshot of its current
internal state.
o
uses the memento to restore its internal state.
·
Caretaker
o
is responsible for the memento's safekeeping.
o
never
operates on or examines the contents of a memento.
·
A caretaker requests a memento from an originator, holds it
for a time, and passes it back to the originator, as the following interaction
diagram illustrates
·
Sometimes the caretaker won't pass the memento back to the
originator, because the originator might never need to revert to an earlier
state.
·
Mementos are passive. Only the originator that created a memento will assign or retrieve
its state.
·
The Memento pattern has several consequences:
o
Preserving
encapsulation boundaries. Memento avoids exposing information that only an
originator should manage but that must be stored nevertheless outside the
originator. The pattern shields other objects from potentially complex
Originator internals, thereby preserving encapsulation boundaries.
o
It
simplifies Originator. In other encapsulation-preserving designs, Originator
keeps the versions of internal state that clients have requested. That puts all
the storage management burden on Originator. Having clients manage the state
they ask for simplifies Originator and keeps clients from having to notify
originators when they're done.
o
Using
mementos might be expensive. Mementos might incur considerable overhead if Originator
must copy large amounts of information to store in the memento or if clients
create and return mementos to the originator often enough. Unless encapsulating
and restoring Originator state is cheap, the pattern might not be appropriate.
o
Defining
narrow and wide interfaces. It may be difficult in some languages to ensure that only
the originator can access the memento's state.
o
Hidden
costs in caring for mementos. A caretaker is responsible for deleting the mementos it
cares for. However, the caretaker has no idea how much state is in the memento.
Hence an otherwise lightweight caretaker might incur large storage costs when
it stores mementos.
·
Related Patterns:
o
Command : Commands can use
mementos to maintain state
for undoable operations.
o
Iterator : Mementos can be used
for iteration